#include <winsock2.h>
#include "MySQL/mysql.h"
#include "pThreads/pthread.h"
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"
#include "CChar.h"
#include "CLinkshell.h"
#include "CDb.h"
#include "CConsole.h"
#include "PacketTypes/CEquipPacket.h"
#include "CItemContainer.h"
#include "CLinkshellItem.h"
#include "CLinkshellMsgPacket.h"
#include "Utils/CWindowClass.h"

/*
TODO:
- unloading linkshells (useful ?)
- code cleanup
*/

T2List<unsigned int,CLinkshell*>* CLinkshell::m_LoadedLinkshells = NULL;

inline void CLinkshell::checkLoadedLinkshells(void)
{
	if (CLinkshell::m_LoadedLinkshells == NULL)
		CLinkshell::m_LoadedLinkshells = new T3ListProtected<unsigned int,CLinkshell*,T2AVLList>();
}

void CLinkshell::addLinkshellToList(CLinkshell* newLinkshell)
{
	CLinkshell::checkLoadedLinkshells();

	CLinkshell::m_LoadedLinkshells->pushFront(newLinkshell->getLsId(), newLinkshell);
}

CLinkshell* CLinkshell::getLinkshellById(unsigned int linkshellId)
{
	CLinkshell::checkLoadedLinkshells();

	CLinkshell* retVal = CLinkshell::m_LoadedLinkshells->find(linkshellId);

	if (retVal != NULL)
		return retVal; //Linkshell is already loaded

	//Load linkshell from the db
	char query[8192];
	unsigned long count;

	MYSQL_RES * res;
	MYSQL_ROW row;
	MYSQL* mysqlConn;

	char lsName[20];
	sprintf(lsName,"");
	char lsMsg[115];
	sprintf(lsMsg,"");
	char setBy[17];
	sprintf(&setBy[0],"");
	int timestamp=0;
	unsigned short lsColor=0;

	sprintf(query,"SELECT lsName, setBy, lsMsg, setOn, color FROM linkshells where  linkshellId = %d LIMIT 1;",linkshellId);
	count = (int) strlen(query);

	mysqlConn = CDb::getConn();
	mysql_real_query(mysqlConn, query, count);
	res = mysql_store_result(mysqlConn);
	if (mysql_errno(mysqlConn) != 0)
		CConsole::outErr("SQL error while querying linkshell with id %d: %s", linkshellId, mysql_error(mysqlConn));
	CDb::pushConn(mysqlConn);

	if ((!res) || (mysql_num_rows(res)==0))
	{
		if (res)
			mysql_free_result(res);

		CConsole::outErr("No linkshell with id %d was found in the db!",linkshellId);
		return NULL; //Linkshell doesn't exist
	}

	row = mysql_fetch_row(res);
	if (row[0]!=NULL)
		strcpy(lsName,row[0]);

	if (row[1]!=NULL)
		strcpy(setBy,row[1]);

	if (row[2]!=NULL)
		strcpy(lsMsg,row[2]);

	if (row[3]!=NULL)
		timestamp=atoi(row[3]);

	if (row[4]!=NULL)
		lsColor=(unsigned short)atoi(row[4]);

	mysql_free_result(res);

	CLinkshell* newLs = new CLinkshell(linkshellId,lsName,lsColor,lsMsg,setBy,timestamp);
	CLinkshell::m_LoadedLinkshells->pushFront(linkshellId,newLs);

	return newLs;
}

//constructors
CLinkshell::CLinkshell()
{
	this->m_LsId = 0;
	this->m_LsColor = 0;
	this->m_LsMesTime = 0;

	memset(this->m_LsName, 0, 20);
	memset(this->m_LsNameComp, 0, 16);
	memset(this->m_LsMes, 0, 115);
	memset(this->m_LsMesPoster, 0, 16);

	this->m_onlineMembers = new T3ListProtected<unsigned int, CChar*, T2AVLList>();
}

CLinkshell::CLinkshell(unsigned int lsId, char* lsName, unsigned short lsColor, char* lsMes, char* lsMesPoster, unsigned int lsMesTime)
{

	this->m_LsId = lsId;
 	this->m_LsColor = lsColor;
	this->m_LsMesTime = lsMesTime;

	memset(this->m_LsName, 0, 20);
	sprintf(this->m_LsName,"");
	if ( lsName!=NULL )
		strncpy(this->m_LsName,lsName, 19);

	memset(this->m_LsNameComp, 0, 16);
	CLinkshell::compressLsName(this->m_LsName, this->m_LsNameComp);

	memset(this->m_LsMes, 0, 115);
	if ( lsMes!=NULL )
		strncpy(this->m_LsMes,lsMes,114);

	memset(this->m_LsMesPoster, 0, 17);
	if ( lsMesPoster!=NULL )
		strncpy(this->m_LsMesPoster,lsMesPoster,16);

	this->m_onlineMembers = new T3ListProtected<unsigned int, CChar*, T2AVLList>();
}

void CLinkshell::addOnlineMember(CChar* newMember)
{
	CConsole::outDebOnly("Adding Member %s (%d) to the online list!", newMember->getName(), newMember->getId());
	m_onlineMembers->pushBack(newMember->getId(), newMember);

	char key[256];
	sprintf(key, "Players online in linkshell %d", this->getLsId());
	CConsole::getOutputWindow()->setValue(key, this->m_onlineMembers->getSize());
}

void CLinkshell::removeOnlineMember(CChar* newMember)
{
	CConsole::outDebOnly("Removing Member %s (%d) from the online list!", newMember->getName(), newMember->getId());
	m_onlineMembers->removeByKey(newMember->getId());

	char key[256];
	sprintf(key, "Players online in linkshell %d", this->getLsId());
	CConsole::getOutputWindow()->setValue(key, this->m_onlineMembers->getSize());
}

T2List<unsigned int,CChar*>* CLinkshell::getOnlineMemberList(void)
{
	return this->m_onlineMembers;
}

//object functions
unsigned int CLinkshell::getLsId(void)
{
	return this->m_LsId;
}

char* CLinkshell::getLsName(void)
{
	return this->m_LsName;
}

char* CLinkshell::getLsNameComp(void)
{
	return this->m_LsNameComp;
}

unsigned short CLinkshell::getLsColor(void)
{
	return this->m_LsColor;
}

char* CLinkshell::getLsMes(void)
{
	return this->m_LsMes;
}

char* CLinkshell::getLsMesPoster(void)
{
	return this->m_LsMesPoster;
}

unsigned int CLinkshell::getLsMesTime(void)
{
	return this->m_LsMesTime;
}

void CLinkshell::setLsMes(char* lsMes, char* lsMesPoster)
{
	memset(this->m_LsMes,0,115);
	if (lsMes!=NULL)
		strncpy(this->m_LsMes, lsMes,114);

	memset(this->m_LsMesPoster,0,17);
	if (lsMesPoster!=NULL)
		strncpy(this->m_LsMesPoster, lsMesPoster,16);

	this->m_LsMesTime = (unsigned int)time(NULL);

	MYSQL* mysqlConn;
	char query[8192];
	unsigned long count;

	char lsMesEscaped[256];
	mysql_escape_string(lsMesEscaped, this->m_LsMes, (unsigned long)strlen(this->m_LsMes));

	char setByEscaped[30];
	mysql_escape_string(setByEscaped, this->m_LsMesPoster, (unsigned long)strlen(this->m_LsMesPoster));

	sprintf(query, "UPDATE linkshells SET lsMsg = '%s', setBy = '%s', setOn = %d where linkshellId = %d;", lsMesEscaped, setByEscaped, this->m_LsMesTime, this->m_LsId);
	CConsole::outDebOnly("Query: %s",query);
	count = (int) strlen(query);

	mysqlConn = CDb::getConn();
	mysql_real_query(mysqlConn, query, count);
	if (mysql_errno(mysqlConn) != 0)
		CConsole::outErr("SQL error while updating linkshell message for linkshell with id %d: %s", this->m_LsId, mysql_error(mysqlConn));
	CDb::pushConn(mysqlConn);
}

void CLinkshell::modifyLinkshellItems(CChar* targetChar, unsigned char operation)
{
	CLinkshellItem * lsItem = targetChar->getLinkshellItem();

	if( lsItem == NULL ) {
		return;
	}

	if ( (operation == 0x02) && (lsItem->getId() == 0x203) ) // Change pearl into sack
		lsItem->setId(0x202);
	else if ( (operation == 0x03) && (lsItem->getId() == 0x202) ) // Change sack into pearl
		lsItem->setId(0x203);

	if ( lsItem->getId() != 0x0000 )
	{
		targetChar->sendItemInvStor(LOC_INVENTORY, targetChar->equip[SLOT_LINKSHELL]);
		targetChar->sendFinishInvStor();
		targetChar->quePacket(CEquipPacket(targetChar->equip[SLOT_LINKSHELL]));
	}
}

void CLinkshell::breakLinkshell(unsigned int linkshellId) {
	MYSQL_RES* res;
	MYSQL_ROW row;
	char query[8192];
	unsigned long count;

	sprintf(query, "DELETE FROM linkshells WHERE linkshellId = %d;", linkshellId);
	count = (int) strlen(query);

	MYSQL* mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);
	if (mysql_errno(mysqlConn) != 0)
		CConsole::outErr("SQL error while deleting linkshell with id %d: %s", linkshellId, mysql_error(mysqlConn));
	CDb::pushConn(mysqlConn); //release conn

	sprintf(query, "SELECT charid FROM char_inventory WHERE addInfoKey = %i GROUP BY charid", linkshellId);
	count = (int) strlen(query);

	mysqlConn = CDb::getConn();
	mysql_real_query(mysqlConn, query, count);
	if (mysql_errno(mysqlConn) != 0)
		CConsole::outErr("SQL error while querying chars for deletion of linkshell with id %d: %s", linkshellId, mysql_error(mysqlConn));
	CDb::pushConn(mysqlConn);

	res = mysql_store_result(mysqlConn);
	if(!res)
		return;
	else if(!mysql_num_rows(res))
		mysql_free_result(res);
	else {
		while(row = mysql_fetch_row(res)) {
			int charid = atoi(row[0]);
			CChar * tempChar = NULL;
			tempChar = CChar::getCChar(charid, "", 0);
			CLinkshell::breakLinkshellItems(tempChar, linkshellId);
		}
	}
	mysql_free_result(res);
}

void CLinkshell::breakLinkshellItems(CChar* targetChar, unsigned int linkshellId)
{
	// If character has linkshell equiped, remove it
	CLinkshellItem * item = NULL;
	item = targetChar->getLinkshellItem();
	if(item != NULL) {
		if(item->getLinkshellId() == linkshellId) {
			char query[8192];
			unsigned long count;

			sprintf(query,"UPDATE char_equip SET link = 0 where charid = %d;",targetChar->m_id);
			count = (int) strlen(query);

			MYSQL* mysqlConn=CDb::getConn(); //get conn
			mysql_real_query(mysqlConn, query, count);
			if (mysql_errno(mysqlConn) != 0)
				CConsole::outErr("SQL error while breaking linkshellitem for char '%s' ( linkshell with id %d ): %s", targetChar->getName(), linkshellId, mysql_error(mysqlConn));
			CDb::pushConn(mysqlConn); //release conn

			//update kicked player
			unsigned char invSlotID = 0;
			invSlotID = targetChar->equip[SLOT_LINKSHELL];

			targetChar->setLinkshellSlotID(0);
			targetChar->quePacket(CEquipPacket(0));
			targetChar->sendUpdateInvStor(invSlotID, LOC_INVENTORY, 1);
			targetChar->sendFinishInvStor();
			targetChar->sendCharUpdate();
		}
	}
	// Break all linkshell items with linkshellId
	for(unsigned char locationID = 0; locationID < 5; locationID++) {
		unsigned char size = 0;
		size = targetChar->getSizeInvStor(locationID);
				
		for(unsigned char slotID=0; slotID < size; slotID++) {
			item = (CLinkshellItem*)targetChar->getItem(locationID, slotID);
			if(item != NULL) {
				if(item->getLinkshellId() == linkshellId) {
					unsigned short brokenItemID = item->getId()+2;
					item->setId(brokenItemID);
					item->setLinkshellId(0);
					targetChar->updateInvStor(locationID, slotID);
					targetChar->sendItemInvStor(locationID, slotID);
				}
			}
		}
	}
	targetChar->sendFinishInvStor();
}

void CLinkshell::sendLsMsg(CChar* receiver)
{
	receiver->lsLoginMessageSent = true;

	if ( receiver->getLinkshell() == NULL )
		return;

	receiver->quePacket(CLinkshellMsgPacket(receiver->getLinkshell()));
}

void CLinkshell::uncompressLsName(char* inputComp, char* outputUncomp)
{
	char tempChar;
	for(int currChar=0; currChar<20; currChar++)
	{
		tempChar=(char)CBasicPacket::unpackBitsLE(inputComp, 6*currChar, 6);

		if ((tempChar>0) && (tempChar<=26))
			outputUncomp[currChar]='a'-1+tempChar;
		else if ((tempChar>0) && (tempChar<=52))
			outputUncomp[currChar]='A'-1+(tempChar-26);

		unsigned char nextBit=( ( 6*(currChar+1) )/8 )*8;
		if (6*(currChar+1)%8!=0)
			nextBit+=8;

		if( 0==(char)CBasicPacket::unpackBitsLE(inputComp, nextBit, 8) )
		{
			outputUncomp[currChar+1]='\0';
			break;
		}
	}
}

void CLinkshell::compressLsName(char* inputUncomp, char* outputComp)
{
	char tempChar;
	for(unsigned char currChar=0;currChar<=strlen(inputUncomp);currChar++)
	{
		if ( (inputUncomp[currChar]>='A') && (inputUncomp[currChar]<='Z') )
			tempChar=inputUncomp[currChar]-'A'+27;
		else if ( (inputUncomp[currChar]>='a') && (inputUncomp[currChar]<='z') )
			tempChar=inputUncomp[currChar]-'a'+1;
		else
			tempChar=(unsigned char)0xFF;//Terminator

		CBasicPacket::packBitsLE((unsigned char*)outputComp, (unsigned int)tempChar, 0, 6*currChar, 6);
	}
}

int CLinkshell::createLinkshellPacket(CChar* schar, char* data)
{
	//Color of the Linkshell ARGB, each 4 bit
	//A is alway F (solid)
	unsigned short linkColor = *(unsigned short*)(data+4);

	//The inventory slot this item is in
	unsigned char invSlotID = *(unsigned char*)(data+6);

	//Equip = 1, Unequip = 0
	unsigned char active = *(unsigned char*)(data+7);

	if (active == 0) // unequip Linkshell
	{
		schar->setLinkshellSlotID(0);
		schar->quePacket(CEquipPacket(0));
		schar->sendUpdateInvStor(invSlotID, LOC_INVENTORY, 1);
		schar->sendFinishInvStor();
		schar->sendCharUpdate();
		return 0;
	}
	// Equip Linkshell
	CLinkshellItem * lsItem = (CLinkshellItem*)schar->getItem(LOC_INVENTORY, invSlotID);
	if ( lsItem == NULL )
		return -1;

	if ( (lsItem->getId() >= 0x201) && (lsItem->getId() <= 0x206) ) //200-204 should be enough
	{ //Equip linkshell item
		unsigned char oldInvSlotID;
		oldInvSlotID = schar->equip[SLOT_LINKSHELL];
		schar->setLinkshellSlotID(invSlotID);
		schar->quePacket(CEquipPacket(invSlotID));
		
		//set the ls in the inventory to active
		//for now: resend the full inventory!
		//better: only send updates for oldLinkshellItemSlot and schar->e.linkshellSlot
		if(oldInvSlotID > 0) {
			schar->setLinkshellSlotID(0);
			schar->sendUpdateInvStor(oldInvSlotID, LOC_INVENTORY, 1);
			schar->sendFinishInvStor();
		}
		schar->setLinkshellSlotID(invSlotID);
		schar->sendItemInvStor(LOC_INVENTORY, invSlotID);
		schar->sendFinishInvStor();
		schar->sendCharUpdate();
		//Send linkshell msg
		//CLinkshell::sendLsMsg(schar);
		return 0;
	}
	else //New Linkshell => create
	{
		// Compressed linkshell name 15 chars, 6 bit for each "real" char
		// last uncompressed char is 0x3F for names with more than 2 chars
		char lsNameComp[15]; 
		memcpy(&lsNameComp[0], data+8, 15);

		char lsName[20]; // 19 chars + terminator
		memset(&lsName, 0, 20);

		CLinkshell::uncompressLsName(&lsNameComp[0], &lsName[0]);

		char query[8192];
		unsigned long count;
		MYSQL_RES* res;
		MYSQL_ROW row;
		MYSQL* mysqlConn;
		sprintf(query, "SELECT * FROM linkshells where lsName like '%s' LIMIT 1;", lsName);
		count = (int) strlen(query);

		mysqlConn = CDb::getConn();

		//Check if the linkshell name is already taken
		mysql_real_query(mysqlConn, query, count);
		res = mysql_store_result(mysqlConn);

		if (mysql_errno(mysqlConn) != 0)
				CConsole::outErr("Failed to query for existing linkshell name: %s",  mysql_error(mysqlConn));

		if( !res )
		{
			CConsole::outErr("Failed to lookup linkshell id for name %s.", lsName);
			CDb::pushConn(mysqlConn);
			return -2;
		}

		if ( mysql_num_rows(res) != 0 )
		{
			CConsole::outDeb("Linkshell name %s is already taken.", lsName);
			//send message to player etc.
			mysql_free_result(res);
			CDb::pushConn(mysqlConn);
			return -3;
		}

		//Add the linkshell name and color to the db and get the linkshellId in return
		sprintf(query, "INSERT INTO linkshells ( linkshellId , lsName , color )");
		sprintf(query, "%s VALUES (NULL,'%s',%d);", query, lsName, linkColor);
		count = (int) strlen(query);
		mysql_real_query(mysqlConn, query, count);

		//get the linkshell id
		sprintf(query, "SELECT linkshellId FROM linkshells WHERE lsName like '%s' LIMIT 1;", lsName);
		count = (int) strlen(query);

		mysql_real_query(mysqlConn, query, count);

		if (mysql_errno(mysqlConn) != 0)
				CConsole::outErr("SQL error while adding new linkshell: %s",  mysql_error(mysqlConn));

		res = mysql_store_result(mysqlConn);

		if (!res)
		{
			CConsole::outErr("Linkshell query for creation of %s returned no result.", lsName);
			CDb::pushConn(mysqlConn);
			return -4;
		}

		if (mysql_num_rows(res)<=0)
		{
			CConsole::outErr("Failed to add linkshell %s to the DB.", lsName);
			if (res)
				mysql_free_result(res);
			CDb::pushConn(mysqlConn);
			return -5;
		}

		//get the linkshell id
		row = mysql_fetch_row(res);
		int lsID;
		lsID=atoi(row[0]);

		if (res)
			mysql_free_result(res);

		//replace the new linkshell with a linkshell (id 0x200 -> 0x201)
		lsItem->setId(0x201);

		//link it with the linkshellId
		lsItem->setLinkshellId(lsID);

		//Update the client
		CDb::pushConn(mysqlConn);
		schar->updateInvStor(LOC_INVENTORY, invSlotID);
		schar->sendItemInvStor(LOC_INVENTORY, invSlotID);
		schar->sendFinishInvStor();
		schar->sendCharUpdate();
		return 0;
	}
	return 1;
}

int CLinkshell::addLinkPearl(CChar* schar, CLinkshell* linkshell)
{
	if (linkshell==NULL)
	{
		CConsole::outDebOnly("Char %s tries to create a linkpearl for a non-existing linkshell.",schar->getName());
		return -1;
	}

	unsigned int lsId = linkshell->getLsId();
	unsigned char newLPSlot = schar->addItem(0x203,0,lsId);

	if (newLPSlot==(unsigned char)0xFF)
	{
		CConsole::outDebOnly("Char %s has no free inventory slot to create a linkpearl.", schar->getName());
		return -2;
	}

	
	//DB related variables
	char query[8192];
	unsigned long count;
	MYSQL* mysqlConn;

	//link the item with the linkshellId
//	sprintf(query, "Update char_inventory SET addInfoKey = %i WHERE charid = %i AND location = 0 AND ", linkshellId);
//	sprintf(query, "INSERT INTO char_inventory_ls_items ( charid , location , linkshellId )"); 
//	sprintf(query, "%s VALUES (%d , '%d', %d);", query, schar->getId(), newLPSlot, lsId);
	sprintf(query, "INSERT INTO char_inventory VALUES (%d, '%d', 0, 515, 1, 1, %d);", schar->getId(), newLPSlot, lsId);
	count = (int)strlen(query);

	mysqlConn = CDb::getConn();
	mysql_real_query(mysqlConn, query, count);
	if (mysql_errno(mysqlConn) != 0)
		CConsole::outErr("SQL error while adding new linkshell item for char '%s' ( linkshell id %d ): %s", schar->getName(), lsId, mysql_error(mysqlConn));
	CDb::pushConn(mysqlConn);
	

	schar->updateInvStor(LOC_INVENTORY, newLPSlot);
	schar->sendFinishInvStor();
	return 0;
}

int CLinkshell::addLinkPearl(CChar* schar)
{
	return addLinkPearl(schar, schar->getLinkshell());
}

